package com.sample.camerax.ui

import android.content.Context
import android.graphics.Point
import android.hardware.display.DisplayManager
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.AspectRatio
import androidx.camera.core.Camera
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.FocusMeteringResult
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.window.layout.WindowMetrics
import androidx.window.layout.WindowMetricsCalculator
import com.loyal.camerax.impl.FocusCallback
import com.loyal.camerax.impl.TouchEventListener
import com.loyal.camerax.utils.ActivityHelper.hideSystemUI
import com.loyal.camerax.utils.PermissionHelper
import com.sample.camerax.analyzer.QrCodeAnalyzer
import com.sample.camerax.databinding.ActivityQrCodeBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

private const val TAG = "QrCodeActivity"
private const val RATIO_4_3_VALUE = 4.0 / 3.0
private const val RATIO_16_9_VALUE = 16.0 / 9.0
private const val IMMERSIVE_FLAG_TIMEOUT = 500L

class QrCodeActivity : AppCompatActivity(), QrCodeAnalyzer.QrCodeParseCallback {
    private val binding by lazy { ActivityQrCodeBinding.inflate(layoutInflater) }
    private var displayId: Int = -1
    private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
    private var preview: Preview? = null
    private var imageCapture: ImageCapture? = null
    private var imageAnalyzer: ImageAnalysis? = null
    private var camera: Camera? = null
    private var cameraInfo: CameraInfo? = null
    private var cameraControl: CameraControl? = null
    private var cameraProvider: ProcessCameraProvider? = null
    private lateinit var windowMetrics: WindowMetrics

    /** Blocking camera operations are performed using this executor */
    private lateinit var cameraExecutor: ExecutorService
    private val displayManager by lazy {
        getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
    }

    /**
     * We need a display listener for orientation changes that do not trigger a configuration
     * change, for example if we choose to override config change in manifest or for 180-degree
     * orientation changes.
     */
    private val displayListener = object : DisplayManager.DisplayListener {
        override fun onDisplayAdded(displayId: Int) = Unit
        override fun onDisplayRemoved(displayId: Int) = Unit
        override fun onDisplayChanged(displayId: Int) = binding.root.let { view ->
            if (displayId == this@QrCodeActivity.displayId) {
                Log.d(TAG, "Rotation changed: ${view.display.rotation}")
                imageCapture?.targetRotation = view.display.rotation
                imageAnalyzer?.targetRotation = view.display.rotation
            }
        }
    }
    private val permissionsLauncher by lazy {
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            if (it.values.contains(false)) {
                Log.e("授权", "未授权")
                finish()
            } else {
                Log.e("授权", "已授权")
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        permissionsLauncher.launch(PermissionHelper.all())
        init()
    }

    private fun init() {
        cameraExecutor = Executors.newSingleThreadExecutor()
        displayManager.registerDisplayListener(displayListener, null)
        windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this)
        // Determine the output directory
        binding.preview.post {
            // Keep track of the display in which this view is attached
            displayId = binding.preview.display.displayId

            // Build UI controls
            updateCameraUi()
            // Set up the camera and its use cases
            setUpCamera()
        }
    }

    /** Initialize CameraX, and prepare to bind the camera use cases  */
    private fun setUpCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            // CameraProvider
            cameraProvider = cameraProviderFuture.get()

            // Select lensFacing depending on the available cameras
            lensFacing = when {
                hasBackCamera() -> CameraSelector.LENS_FACING_BACK
                hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
                else -> throw IllegalStateException("Back and front camera are unavailable")
            }

            // Build and bind the camera use cases
            bindCameraUseCases()
        }, ContextCompat.getMainExecutor(this))
    }

    /** Declare and bind preview, capture and analysis use cases */
    private fun bindCameraUseCases() {
        // Get screen metrics used to setup camera for full screen resolution
        val metrics = windowMetrics.bounds
        Log.d(TAG, "Screen metrics: ${metrics.width()} x ${metrics.height()}")

        val screenAspectRatio = aspectRatio(metrics.width(), metrics.height())
        Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")

        val rotation = binding.preview.display.rotation

        // CameraProvider
        val cameraProvider = cameraProvider
            ?: throw IllegalStateException("Camera initialization failed.")

        // 选择相机
        val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
        val resolutionSelector = ResolutionSelector.Builder()
            .setAspectRatioStrategy(screenAspectRatio)
            .build()
        // 预览
        preview = Preview.Builder()
            // We request aspect ratio but no resolution
            .setResolutionSelector(resolutionSelector)
            // Set initial target rotation
            .setTargetRotation(rotation)
            .build()

        // 抓拍
        imageCapture = ImageCapture.Builder()
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            // We request aspect ratio but no resolution to match preview config, but letting
            // CameraX optimize for whatever specific resolution best fits our use cases
            .setResolutionSelector(resolutionSelector)
            // Set initial target rotation, we will have to call this again if rotation changes
            // during the lifecycle of this use case
            .setTargetRotation(rotation)
            .build()

        // 图片分析
        imageAnalyzer = ImageAnalysis.Builder()
            // We request aspect ratio but no resolution
            .setResolutionSelector(resolutionSelector)
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            // Set initial target rotation, we will have to call this again if rotation changes
            // during the lifecycle of this use case
            .setTargetRotation(rotation)
            .build()
            // The analyzer can then be assigned to the instance
            .also {
                it.setAnalyzer(
                    cameraExecutor, QrCodeAnalyzer(this, File(filesDir, "test.jpg").path)
                )
            }

        // Must unbind the use-cases before rebinding them
        cameraProvider.unbindAll()

        try {
            // A variable number of use-cases can be passed here -
            // camera provides access to CameraControl & CameraInfo
            camera = cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture, imageAnalyzer
            )

            // Attach the viewfinder's surface provider to preview use case
            preview?.setSurfaceProvider(binding.preview.surfaceProvider)

            cameraControl = camera?.cameraControl
            cameraInfo = camera?.cameraInfo
            //缩放和聚焦
            zoomAndFocus()
        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }
    }

    private fun zoomAndFocus() {
        val preview = binding.preview
        val focusView = binding.focusView
        val zoomState = cameraInfo?.zoomState
        val cameraXPreviewViewTouchListener = TouchEventListener(this)
        cameraXPreviewViewTouchListener.setFocusCallback(object : FocusCallback {
            override fun zoom(delta: Float) {
                Log.e(TAG, "zoom: ->$delta")
                zoomState?.value?.let {
                    val currentZoomRatio = it.zoomRatio
                    cameraControl?.setZoomRatio(currentZoomRatio * delta)
                }
            }

            override fun click(x: Float, y: Float) {
                Log.e(TAG, "click: ")
                val factory = preview.meteringPointFactory
                val point = factory.createPoint(x, y)
                val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                    .setAutoCancelDuration(3, TimeUnit.SECONDS)
                    .build()
                focusView.startFocus(Point(x.toInt(), y.toInt()))
                val future = cameraControl?.startFocusAndMetering(action)
                future?.let {
                    it.addListener({
                        try {
                            val result = it.get() as FocusMeteringResult
                            focusView.focus(result.isFocusSuccessful)
                        } catch (e: Exception) {
                            e.printStackTrace()
                            focusView.focusError(e)
                        }
                    }, cameraExecutor)
                }
            }

            override fun doubleClick(x: Float, y: Float) {
                Log.e(TAG, "doubleClick: ")
                // 双击放大缩小
                zoomState?.value?.let {
                    val currentZoomRatio = it.zoomRatio
                    if (currentZoomRatio > it.minZoomRatio) {
                        cameraControl?.setLinearZoom(0f)
                    } else {
                        cameraControl?.setLinearZoom(0.5f)
                    }
                }
            }

            override fun longClick(x: Float, y: Float) {
                Log.e(TAG, "longClick: ")
            }
        })
        binding.preview.setOnTouchListener(cameraXPreviewViewTouchListener)
    }

    /**
     *  [androidx.camera.core.ImageAnalysis.Builder] requires enum value of
     *  [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
     *
     *  Detecting the most suitable ratio for dimensions provided in @params by counting absolute
     *  of preview ratio to one of the provided values.
     *
     *  @param width - preview width
     *  @param height - preview height
     *  @return suitable aspect ratio
     */
    private fun aspectRatio(width: Int, height: Int): AspectRatioStrategy {
        val previewRatio = max(width, height).toDouble() / min(width, height)
        if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
            return AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY
        }
        return AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY
    }

    private fun updateCameraUi() {
        // In the background, load latest photo taken (if any) for gallery thumbnail
        lifecycleScope.launch(Dispatchers.IO) {
            //todo
            /*outputDirectory.listFiles { file ->
                EXTENSION_WHITELIST.contains(file.extension.uppercase(Locale.ROOT))
            }?.maxOrNull()?.let {
                setGalleryThumbnail(Uri.fromFile(it))
            }*/
        }
        /*binding.cameraTorchButton.setOnClickListener {
            if (TorchState.ON == cameraInfo?.torchState?.value) {
                cameraControl?.enableTorch(false)
            } else {
                cameraControl?.enableTorch(true)
            }
            //camera?.cameraInfo?.hasFlashUnit()
        }*/
        //手电筒:
        /*binding.flashSwitchButton.setOnClickListener {
            //闪光灯模式
            imageCapture?.flashMode
            imageCapture?.flashMode = ImageCapture.FLASH_MODE_AUTO
        }*/
        // Listener for button used to capture photo
    }

    /** Returns true if the device has an available back camera. False otherwise */
    private fun hasBackCamera(): Boolean {
        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
    }

    /** Returns true if the device has an available front camera. False otherwise */
    private fun hasFrontCamera(): Boolean {
        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
    }

    override fun onBackPressed() {
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
            // Workaround for Android Q memory leak issue in IRequestFinishCallback$Stub.
            // (https://issuetracker.google.com/issues/139738913)
            finishAfterTransition()
        } else {
            super.onBackPressed()
        }
    }

    override fun onResume() {
        super.onResume()
        // Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
        // be trying to set app to immersive mode before it's ready and the flags do not stick
        binding.root.postDelayed({
            hideSystemUI()
        }, IMMERSIVE_FLAG_TIMEOUT)
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
        displayManager.unregisterDisplayListener(displayListener)
    }

    override fun success(text: String?) {
        finish()
        Log.e(TAG, "success!!! = $text")
        imageAnalyzer?.clearAnalyzer()
        Log.e(TAG, "success: =" + Thread.currentThread().name)
        runOnUiThread {
            Log.e(TAG, "success: =" + Thread.currentThread().name)
            if (!TextUtils.isEmpty(text)) {
                val builder = AlertDialog.Builder(this)
                builder.setTitle("扫码结果").setMessage(text)
                    .setPositiveButton("确定") { dialog, _ ->
                        dialog?.dismiss()
                        finish()
                    }
                builder.show()
            }
        }
    }

    override fun error(e: Exception) {
        Log.d(TAG, "error: ", e)
    }
}